using System;
using System.Linq;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration;
using System.Net.Http;
using System.Net.Http.Json;
using RBAC.Read.Api.Application.Options;

namespace RBAC.Read.Api.Application.Services
{
    /// <summary>
    /// OpenWeather 访问封装。
    /// 负责：
    /// 1) 城市名 → 经纬度（Geocoding API）
    /// 2) One Call 3.0 获取当前/小时级天气
    /// 3) 本地缓存（IMemoryCache）减少配额与延迟
    /// 说明：API Key 从环境变量 <c>OPENWEATHER_KEY</c> 优先读取，或使用配置 Weather:OpenWeatherKey。
    /// </summary>
    public class WeatherService : IWeatherService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IMemoryCache _cache;
        private readonly ILogger<WeatherService> _logger;
        private readonly WeatherOptions _opts;
        private readonly string _apiKey;
        private static readonly System.Collections.Generic.Dictionary<string, string> WeatherMainZh = new()
        {
            ["Thunderstorm"] = "雷暴",
            ["Drizzle"] = "毛毛雨",
            ["Rain"] = "雨",
            ["Snow"] = "雪",
            ["Mist"] = "薄雾",
            ["Smoke"] = "烟雾",
            ["Haze"] = "霾",
            ["Dust"] = "扬尘",
            ["Fog"] = "雾",
            ["Sand"] = "沙尘",
            ["Ash"] = "火山灰",
            ["Squall"] = "飑",
            ["Tornado"] = "龙卷风",
            ["Clear"] = "晴",
            ["Clouds"] = "多云"
        };
        private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
            Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
        };

        public WeatherService(IHttpClientFactory httpClientFactory,
            IMemoryCache cache,
            ILogger<WeatherService> logger,
            IOptions<WeatherOptions> opts,
            IConfiguration config)
        {
            _httpClientFactory = httpClientFactory;
            _cache = cache;
            _logger = logger;
            _opts = opts.Value;
            // 优先使用非空的环境变量，其次使用非空的配置项；均为空则抛错
            var envKey = Environment.GetEnvironmentVariable("OPENWEATHER_KEY");
            var cfgKey = _opts.OpenWeatherKey;
            if (!string.IsNullOrWhiteSpace(envKey))
            {
                _apiKey = envKey.Trim();
            }
            else if (!string.IsNullOrWhiteSpace(cfgKey))
            {
                _apiKey = cfgKey.Trim();
            }
            else
            {
                throw new InvalidOperationException("OpenWeather API Key not configured. Set env OPENWEATHER_KEY or Weather:OpenWeatherKey");
            }
        }

        /// <summary>
        /// 将城市名解析为经纬度（使用 OpenWeather Geocoding API）。
        /// </summary>
        /// <param name="city">城市名称（支持中文/英文）</param>
        /// <returns>包含纬度与经度的 <see cref="WeatherLocation"/></returns>
        public async Task<WeatherLocation> ResolveCityAsync(string city)
        {
            var client = _httpClientFactory.CreateClient("weather");
            var url = $"{_opts.GeoBase}?q={Uri.EscapeDataString(city)}&limit=1&appid={_apiKey}";
            var cacheKey = $"geo:{city}";
            // 命中缓存则直接返回，降低第三方调用频率
            if (_cache.TryGetValue(cacheKey, out WeatherLocation cachedLoc)) return cachedLoc;
            using var res = await client.GetAsync(url);
            if (!res.IsSuccessStatusCode)
            {
                var body = await res.Content.ReadAsStringAsync();
                if ((int)res.StatusCode == 401)
                {
                    throw new UnauthorizedAccessException($"OpenWeather unauthorized (geocoding). Detail: {body}");
                }
                throw new HttpRequestException($"Geocoding failed: {(int)res.StatusCode} {res.ReasonPhrase}. Detail: {body}");
            }
            var arr = await res.Content.ReadFromJsonAsync<JsonElement[]>(_jsonOptions) ?? Array.Empty<JsonElement>();
            if (arr.Length == 0) throw new InvalidOperationException("City not found");
            var lat = arr[0].GetProperty("lat").GetDouble();
            var lon = arr[0].GetProperty("lon").GetDouble();
            var loc = new WeatherLocation { Lat = lat, Lon = lon };
            _cache.Set(cacheKey, loc, TimeSpan.FromMinutes(_opts.CacheTtlMinutes));
            return loc;
        }

        /// <summary>
        /// 获取当前天气（温度/湿度/风速等）。
        /// </summary>
        /// <param name="lat">纬度</param>
        /// <param name="lon">经度</param>
        /// <param name="units">单位体系：metric/imperial</param>
        /// <returns><see cref="CurrentWeatherDto"/></returns>
        public async Task<CurrentWeatherDto> GetCurrentAsync(double lat, double lon, string units = "metric")
        {
            // 使用免费 data/2.5/weather 接口获取“当前天气”，避免 One Call 3.0 的付费限制
            var data = await CurrentWeatherApiAsync(lat, lon, units);
            var main = data.GetProperty("main");
            var wind = data.TryGetProperty("wind", out var w) ? w : default;
            var weather0 = data.TryGetProperty("weather", out var wa) && wa.ValueKind == JsonValueKind.Array && wa.GetArrayLength() > 0 ? wa[0] : default;
            string? mainEn = weather0.ValueKind != JsonValueKind.Undefined && weather0.TryGetProperty("main", out var wm0) ? wm0.GetString() : null;
            string? mainZh = mainEn != null && WeatherMainZh.TryGetValue(mainEn, out var mapped) ? mapped : mainEn;
            return new CurrentWeatherDto
            {
                Location = new WeatherLocation { Lat = lat, Lon = lon },
                Units = units,
                Temp = main.GetProperty("temp").GetDouble(),
                Humidity = main.GetProperty("humidity").GetInt32(),
                WindSpeed = wind.ValueKind != JsonValueKind.Undefined && wind.TryGetProperty("speed", out var ws) ? ws.GetDouble() : 0,
                WeatherMain = mainZh,
                WeatherDesc = weather0.ValueKind != JsonValueKind.Undefined && weather0.TryGetProperty("description", out var wd) ? wd.GetString() : null
            };
        }

        /// <summary>
        /// 获取未来 N 小时的逐小时预报。
        /// </summary>
        /// <param name="lat">纬度</param>
        /// <param name="lon">经度</param>
        /// <param name="hours">小时数（1-48）</param>
        /// <param name="units">单位体系：metric/imperial</param>
        /// <returns><see cref="ForecastDto"/></returns>
        public async Task<ForecastDto> GetForecastAsync(double lat, double lon, int hours = 72, string units = "metric")
        {
            // 使用免费 data/2.5/forecast 接口（3 小时步长）
            var data = await ForecastApiAsync(lat, lon, units);
            var result = new ForecastDto
            {
                Location = new WeatherLocation { Lat = lat, Lon = lon },
                Units = units
            };
            if (data.TryGetProperty("list", out var list) && list.ValueKind == JsonValueKind.Array)
            {
                // 将每 3 小时一个点的序列线性插值为“每小时一个点”，最多返回 'hours' 个点
                int target = Math.Max(1, hours);
                for (int i = 0; i < list.GetArrayLength() - 1 && result.Hourly.Count < target; i++)
                {
                    var p0 = list[i];
                    var p1 = list[i + 1];
                    var m0 = p0.GetProperty("main");
                    var m1 = p1.GetProperty("main");
                    var w0 = p0.TryGetProperty("wind", out var wv0) ? wv0 : default;
                    var w1 = p1.TryGetProperty("wind", out var wv1) ? wv1 : default;
                    double t0 = m0.GetProperty("temp").GetDouble();
                    double t1 = m1.GetProperty("temp").GetDouble();
                    int h0 = m0.GetProperty("humidity").GetInt32();
                    int h1 = m1.GetProperty("humidity").GetInt32();
                    double ws0 = w0.ValueKind != JsonValueKind.Undefined && w0.TryGetProperty("speed", out var sp0) ? sp0.GetDouble() : 0;
                    double ws1 = w1.ValueKind != JsonValueKind.Undefined && w1.TryGetProperty("speed", out var sp1) ? sp1.GetDouble() : 0;
                    double pop0 = p0.TryGetProperty("pop", out var popv0) ? popv0.GetDouble() : 0;
                    double pop1 = p1.TryGetProperty("pop", out var popv1) ? popv1.GetDouble() : 0;
                    // 每个 3 小时间隔的降雨量 3h 累计，均分到 3 个小时
                    double rain3h = 0;
                    if (p0.TryGetProperty("rain", out var r0) && r0.ValueKind == JsonValueKind.Object && r0.TryGetProperty("3h", out var r3))
                        rain3h = r3.GetDouble();
                    double rainPerHour = rain3h / 3.0;

                    long dt0 = p0.GetProperty("dt").GetInt64();
                    long dt1 = p1.GetProperty("dt").GetInt64();
                    // 防御：如不是 3 小时间隔，也按比例插值
                    double totalSec = Math.Max(1, dt1 - dt0);

                    // 天气主类中文映射：前两小时用 p0，最后一小时用 p1
                    string? mainEn0 = p0.TryGetProperty("weather", out var wa0) && wa0.ValueKind == JsonValueKind.Array && wa0.GetArrayLength() > 0 && wa0[0].TryGetProperty("main", out var wm0)
                        ? wm0.GetString() : null;
                    string? mainEn1 = p1.TryGetProperty("weather", out var wa1) && wa1.ValueKind == JsonValueKind.Array && wa1.GetArrayLength() > 0 && wa1[0].TryGetProperty("main", out var wm1)
                        ? wm1.GetString() : null;
                    string? mainZh0 = mainEn0 != null && WeatherMainZh.TryGetValue(mainEn0, out var map0) ? map0 : mainEn0;
                    string? mainZh1 = mainEn1 != null && WeatherMainZh.TryGetValue(mainEn1, out var map1) ? map1 : mainEn1;

                    // 生成当前间隔内的 3 个小时点
                    for (int h = 0; h < 3 && result.Hourly.Count < target; h++)
                    {
                        double sec = h * 3600.0;
                        double ratio = Math.Clamp(sec / totalSec, 0, 1);
                        long dtSec = dt0 + (long)sec;
                        var local = DateTimeOffset.FromUnixTimeSeconds(dtSec).ToLocalTime();
                        var hp = new HourlyPoint
                        {
                            Dt = dtSec,
                            Year = local.Year,
                            Month = local.Month,
                            Day = local.Day,
                            Hour = local.Hour,
                            Temp = t0 + (t1 - t0) * ratio,
                            Humidity = (int)Math.Round(h0 + (h1 - h0) * ratio),
                            WindSpeed = ws0 + (ws1 - ws0) * ratio,
                            Pop = Math.Clamp(pop0 + (pop1 - pop0) * ratio, 0, 1),
                            Rain = rainPerHour,
                            WeatherMain = h < 2 ? mainZh0 : mainZh1
                        };
                        result.Hourly.Add(hp);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 封装 One Call 3.0 请求与缓存逻辑。
        /// </summary>
        /// <remarks>
        /// - 缓存键包含纬度/经度/单位，默认缓存 <c>CacheTtlMinutes</c>。
        /// - 排除了 <c>minutely</c> 分钟级数据，以降低数据量。
        /// </remarks>
        private async Task<JsonElement> OneCallAsync(double lat, double lon, string units)
        {
            var cacheKey = $"onecall:{lat:F4}:{lon:F4}:{units}";
            if (_cache.TryGetValue(cacheKey, out JsonElement cached)) return cached;

            var client = _httpClientFactory.CreateClient("weather");
            var url = $"{_opts.OneCallBase}?lat={lat}&lon={lon}&appid={_apiKey}&units={units}&exclude=minutely&lang={_opts.Lang}";
            using var res = await client.GetAsync(url);
            if (!res.IsSuccessStatusCode)
            {
                var body = await res.Content.ReadAsStringAsync();
                if ((int)res.StatusCode == 401)
                {
                    throw new UnauthorizedAccessException($"OpenWeather unauthorized (onecall). Detail: {body}");
                }
                throw new HttpRequestException($"OneCall failed: {(int)res.StatusCode} {res.ReasonPhrase}. Detail: {body}");
            }
            var json = await res.Content.ReadFromJsonAsync<JsonElement>(_jsonOptions);
            _cache.Set(cacheKey, json, TimeSpan.FromMinutes(_opts.CacheTtlMinutes));
            return json;
        }

        /// <summary>
        /// 使用免费 data/2.5/weather 接口获取“当前天气”（含缓存与错误处理）。
        /// </summary>
        private async Task<JsonElement> CurrentWeatherApiAsync(double lat, double lon, string units)
        {
            var cacheKey = $"current:{lat:F4}:{lon:F4}:{units}";
            if (_cache.TryGetValue(cacheKey, out JsonElement cached)) return cached;

            var client = _httpClientFactory.CreateClient("weather");
            var url = $"{_opts.WeatherBase}?lat={lat}&lon={lon}&appid={_apiKey}&units={units}&lang={_opts.Lang}";
            using var res = await client.GetAsync(url);
            if (!res.IsSuccessStatusCode)
            {
                var body = await res.Content.ReadAsStringAsync();
                if ((int)res.StatusCode == 401)
                {
                    throw new UnauthorizedAccessException($"OpenWeather unauthorized (weather). Detail: {body}");
                }
                throw new HttpRequestException($"Weather(current) failed: {(int)res.StatusCode} {res.ReasonPhrase}. Detail: {body}");
            }
            var json = await res.Content.ReadFromJsonAsync<JsonElement>(_jsonOptions);
            _cache.Set(cacheKey, json, TimeSpan.FromMinutes(_opts.CacheTtlMinutes));
            return json;
        }

        /// <summary>
        /// 使用免费 data/2.5/forecast 获取 3 小时步长的预报（含缓存与错误处理）。
        /// </summary>
        private async Task<JsonElement> ForecastApiAsync(double lat, double lon, string units)
        {
            var cacheKey = $"forecast:{lat:F4}:{lon:F4}:{units}";
            if (_cache.TryGetValue(cacheKey, out JsonElement cached)) return cached;

            var client = _httpClientFactory.CreateClient("weather");
            var url = $"{_opts.ForecastBase}?lat={lat}&lon={lon}&appid={_apiKey}&units={units}&lang={_opts.Lang}";
            using var res = await client.GetAsync(url);
            if (!res.IsSuccessStatusCode)
            {
                var body = await res.Content.ReadAsStringAsync();
                if ((int)res.StatusCode == 401)
                {
                    throw new UnauthorizedAccessException($"OpenWeather unauthorized (forecast). Detail: {body}");
                }
                throw new HttpRequestException($"Forecast(2.5) failed: {(int)res.StatusCode} {res.ReasonPhrase}. Detail: {body}");
            }
            var json = await res.Content.ReadFromJsonAsync<JsonElement>(_jsonOptions);
            _cache.Set(cacheKey, json, TimeSpan.FromMinutes(_opts.CacheTtlMinutes));
            return json;
        }
    }
}
